library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_signed.all;

entity cu is
	generic
	(
		OP_WIDTH	: natural  :=	6;	
		FUNC_WIDTH	: natural  :=	6;
		REG_WIDTH	: natural  := 	5
	);


	port
	(
		-- Input ports
		instr	: in  std_logic_vector(31 downto 0);
		irqmask : in std_logic;
		-- from pipeid
		rsrtequ	: in std_logic;	-- if rs = rt
		-- Pipeline reg state
		mwmem : in std_logic;
		mwreg : in std_logic;
		mm2reg : in std_logic;
		mdesr : in std_logic_vector(REG_WIDTH-1 downto 0);
		ewreg : in std_logic;
		em2reg : in std_logic;
		edesr : in std_logic_vector(REG_WIDTH-1 downto 0);
		dcacheok : in std_logic;
		
		-- Output ports
		cen 	: out std_logic;
		fwda	: out std_logic_vector(1 downto 0);
		fwdb	: out std_logic_vector(1 downto 0);
		wpcir	: out std_logic; -- write instruction register
		jump	: out std_logic;
		branch	: out std_logic;
		jpatch	: out std_logic;
		iret	: out std_logic;
		writereg	: out std_logic; -- wreg
		regdes	: out std_logic; -- regrt
		writemem	: out std_logic; -- wmem
		memtoreg	: out std_logic; -- m2reg
		shift	: out std_logic;
		aluop	: out std_logic_vector (4 downto 0);
		alusrcb	: out std_logic;-- aluimm
		se	: out std_logic -- sext
	);
end cu;

architecture rtl_cu of cu is
begin

	process (instr)
		variable op: std_logic_vector(OP_WIDTH-1 downto 0) := instr(31 downto 26);
	    variable func: std_logic_vector (FUNC_WIDTH-1 downto 0):= instr(5 downto 0);
	-- instruction type R
	    variable special: std_logic := (not op(5)) and (not op(4)) and (not op(3)) and (not op(2)) and (not op(1)) and (not op(0));
		variable i_add: std_logic := special and func(5) and (not func(4)) and (not func(3)) and (not func(2)) and (not func(1)) and (not func(0));
		variable i_sub: std_logic := special and func(5) and (not func(4)) and (not func(3)) and (not func(2)) and func(1) and (not func(0));
		variable i_and: std_logic := special and func(5) and (not func(4)) and (not func(3)) and func(2) and (not func(1)) and (not func(0));
		variable i_or: std_logic := special and func(5) and (not func(4)) and (not func(3)) and func(2) and (not func(1)) and func(0);
		variable i_xor: std_logic := special and func(5) and (not func(4)) and (not func(3)) and func(2) and func(1) and (not func(0));
		variable i_slt: std_logic := special and func(5) and (not func(4)) and func(3) and (not func(2)) and func(1) and (not func(0));
		variable i_sll: std_logic := special and (not func(5)) and (not func(4)) and (not func(3)) and (not func(2)) and (not func(1)) and (not func(0));
		variable i_srl: std_logic := special and (not func(5)) and (not func(4)) and (not func(3)) and (not func(2)) and (func(1)) and (not func(0));
		variable i_sra: std_logic := special and (not func(5)) and (not func(4)) and (not func(3)) and (not func(2)) and (func(1)) and (func(0));
	-- instruction type I
		variable i_lw: std_logic := op(5) and (not op(4)) and (not op(3)) and (not op(2)) and op(1) and op(0);
		variable i_sw: std_logic := op(5) and (not op(4)) and op(3) and (not op(2)) and op(1) and op(0);
		variable i_addi: std_logic := (not op(5)) and (not op(4)) and op(3) and (not op(2)) and (not op(1)) and (not op(0));
		variable i_andi: std_logic := (not op(5)) and (not op(4)) and op(3) and op(2) and (not op(1)) and (not op(0));
		variable i_ori: std_logic := (not op(5)) and (not op(4)) and op(3) and op(2) and (not op(1)) and op(0);
		variable i_beq: std_logic := (not op(5)) and (not op(4)) and (not op(3)) and op(2) and (not op(1)) and (not op(0));
		variable i_bne: std_logic := (not op(5)) and (not op(4)) and (not op(3)) and op(2) and (not op(1)) and op(0);
	-- instruction type J
		variable i_jmp: std_logic := (not op(5)) and (not op(4)) and (not op(3)) and (not op(2)) and op(1) and (not op(0));
		variable i_ret: std_logic := (not op(5)) and (not op(4)) and (not op(3)) and op(2) and op(1) and op(0);
	-- for pipeline stall
		variable rs: std_logic_vector(REG_WIDTH-1 downto 0) := instr(25 downto 21);
		variable i_rs: std_logic := i_add or i_sub or i_and or i_or or i_xor or i_addi or i_andi or i_ori or i_lw or i_sw or i_beq or i_bne;
		variable rt: std_logic_vector(REG_WIDTH-1 downto 0) := instr(20 downto 16);
		variable i_rt: std_logic := i_add or i_sub or i_and or i_or or i_xor or i_sll or i_srl or i_sra or i_sw or i_beq or i_bne;
		
	begin
		jump <= i_jmp and (not irqmask);
		jpatch <= (i_jmp or (rsrtequ and i_beq) or ((not rsrtequ) and i_bne)) and irqmask;
		branch <= (i_jmp or (rsrtequ and i_beq) or ((not rsrtequ) and i_bne)) and (not irqmask);
		iret <= i_ret;
		pipestall: if (ewreg = '1') and (em2reg = '1') and (edesr /= "00000") and 
					  (((i_rs = '1') and (edesr = rs)) or ((i_rt = '1') and (edesr = rt))) then
			wpcir <= '0';
			writereg <= '0';
			writemem <= '0';
		else
			wpcir <= '1';
			writereg <= special or i_lw or i_addi or i_andi or i_ori;
			writemem <= i_sw;
		end if;
		regdes <= special;		
		memtoreg <= i_lw;
		shift <= i_sll or i_srl or i_sra;
		aluop(4) <= i_xor or i_sll;
		aluop(3) <= i_sub or i_or or i_slt or i_ori or i_xor or i_sra or i_beq or i_bne;
		aluop(2) <= i_srl or i_sll or i_sra;
		aluop(1) <= i_slt;
		aluop(0) <= i_add or i_sub or i_lw or i_sw or i_addi or i_beq or i_bne;
		alusrcb <= i_addi or i_andi or i_ori or i_lw or i_sw;
		se <= i_addi or i_lw or i_sw or i_beq or i_bne;
		
		if (mwmem = '1' or mm2reg = '1') then
			cen <= dcacheok;
		else
			cen <= '1';
		end if;
	end process;
	
	p_fwda: process(instr, ewreg, mwreg, edesr, mdesr, mm2reg)
		variable rs: std_logic_vector(REG_WIDTH-1 downto 0) := instr(25 downto 21);
	begin
		fwda <= "00";
		if (ewreg = '1') and (edesr /= "00000") and (edesr = rs) then
			fwda <= "01";
		elsif (mwreg = '1') and (mdesr /= "00000") and (mdesr = rs) and (mm2reg /= '1') then
			fwda <= "10";
		elsif (mwreg = '1') and (mdesr /= "00000") and (mdesr = rs) and (mm2reg = '1') then
			fwda <= "11";
		end if;
	end process;
	
	p_fwdb: process(instr, ewreg, mwreg, edesr, mdesr, mm2reg)
		variable rt: std_logic_vector(REG_WIDTH-1 downto 0) := instr(20 downto 16);
	begin
		fwdb <= "00";
		if (ewreg = '1') and (edesr /= "00000") and (edesr = rt) then
			fwdb <= "01";
		elsif (mwreg = '1') and (mdesr /= "00000") and (mdesr = rt) and (mm2reg /= '1') then
			fwdb <= "10";
		elsif (mwreg = '1') and (mdesr /= "00000") and (mdesr = rt) and (mm2reg = '1') then
			fwdb <= "11";
		end if;
	end process;
end rtl_cu;
